Dates and times topic

Timezones & Locales

This is part of the kalender documentation.

Locale

Kalender uses the intl package to localize day and month names. Call initializeDateFormatting() before runApp:

import 'package:intl/date_symbol_data_local.dart';

void main() async {
  await initializeDateFormatting();
  runApp(const MyApp());
}

The function comes from date_symbol_data_local.dart, not from intl.dart. The intl package compiles in the en_US data only, so every other locale needs this call, including en. Without it, kalender throws an error naming the locale that failed and the call to add.

KalenderView has a locale property that controls day/month name formatting. It takes a Locale, and Localizations.localeOf(context) gives you the app's.

KalenderView(
  locale: const Locale('af', 'ZA'),
  eventsController: eventsController,
  kalenderController: kalenderController,
)

The week number tooltip is the one string that defaults to English:

MaterialApp(
  theme: ThemeData(
    extensions: [
      KalenderThemeData(
        weekNumberStyle: WeekNumberStyle(tooltip: 'Weeknummer'),
      ),
    ],
  ),
)

See Theming.

Custom text

Apart from the week number tooltip, every string the calendar writes can be replaced with a string builder on the matching *Components class. Each one receives the BuildContext, so it can read the calendar's own locale with context.kalenderLocale, which is not necessarily the app's locale. intl takes a string, so pass toLanguageTag():

import 'package:intl/intl.dart';

KalenderView(
  locale: const Locale('af', 'ZA'),
  eventsController: eventsController,
  kalenderController: kalenderController,
  components: KalenderComponents(
    multiDayComponents: MultiDayComponents(
      headerComponents: MultiDayHeaderComponents(
        dayHeaderStringBuilder: (context, date) => DateFormat.E(context.kalenderLocale?.toLanguageTag()).format(date),
      ),
    ),
    overlayBuilders: OverlayBuilders(
      multiDayPortalOverlayButtonStringBuilder: (context, n) => '$n meer',
    ),
  ),
)

The builders are dayHeaderStringBuilder and dayHeaderNumberStringBuilder on MultiDayHeaderComponents, timelineStringBuilder on MultiDayBodyComponents, monthDayHeaderStringBuilder on MonthBodyComponents, weekDayHeaderStringBuilder on MonthHeaderComponents, leadingDateStringBuilder on ScheduleComponents, and multiDayPortalOverlayButtonStringBuilder on OverlayBuilders.

The timeline follows MaterialLocalizations when the app installs them, so it uses the device's 12-hour or 24-hour setting. Otherwise it follows the calendar's locale. Fix the format with timelineStringBuilder:

MultiDayBodyComponents(
  timelineStringBuilder: (context, time) =>
      '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}',
)

Location

KalenderController accepts a Location from the timezone package. The KalenderEvent constructor automatically converts start and end to UTC, so events are always stored in UTC internally and converted to the given location for display.

import 'package:timezone/timezone.dart' as tz;

KalenderController(
  viewConfiguration: viewConfiguration,
  location: tz.getLocation('America/New_York'),
)

Pre-initialize DefaultEventsController with the locations you expect to query for best performance:

import 'package:timezone/timezone.dart' as tz;

final eventsController = DefaultEventsController(
  locations: [
    tz.getLocation('America/New_York'),
    tz.getLocation('Europe/London'),
    tz.getLocation('Asia/Tokyo'),
  ],
);

See the timezone package for setup instructions per platform. The web demo also provides a working example.

Setting the controller's location at runtime recreates the view in the new location. Location identifiers follow the IANA Time Zone Database.

Events from an external source

When events come from an .ics file, a device calendar, or an API, map each source time to the exact instant it represents before building the KalenderEvent. The constructor stores the instant as UTC, so what matters is that the DateTime you pass points at the right moment.

  • UTC instant (an .ics time ending in Z, or an epoch): pass it as-is.

  • Zoned time (an IANA TZID): build a TZDateTime in that zone so the instant is correct.

    import 'package:timezone/timezone.dart' as tz;
    
    final start = tz.TZDateTime(tz.getLocation('Europe/London'), 2025, 1, 6, 9);
    final event = KalenderEvent(
      start: start, end: start.add(const Duration(hours: 1)),
    );
    
  • Floating time (no zone, common in .ics): decide which zone it should mean, usually the calendar's location, and build a TZDateTime there.

Then set the controller's location to the zone the calendar should display in. The ics example shows this end to end.

Now Callback

By default, the time indicator position and "today" header highlighting are derived from the calendar's Location. To resolve "now" differently from the calendar's Location, pass a NowCallback:

MultiDayViewConfiguration.week(
  nowCallback: DateTime.now, // system local time
)

The callback's return value is used for:

  • Positioning the time indicator on the calendar grid.
  • Determining which day is "today" for header highlighting (DayHeader, MonthDayHeader, ScheduleDate).
  • Evaluating EmptyDayBehavior.showOnlyToday in schedule views.

Any DateTime subtype works, so the callback can return UTC or a TZDateTime in a specific zone.

nowCallback is included in ==, so store a closure rather than writing it inline.

When nowCallback is null (the default), the calendar falls back to its Location-based behavior.

Classes

FloatingDateTime Dates and times
A date and time with no timezone, used for calendar layout.
FloatingDateTimeRange Dates and times
A range between two FloatingDateTimes, used for calendar layout.
KalenderDateTimeRange Dates and times
A range between two DateTimes. FloatingDateTimeRange.forLocation converts to it, FloatingDateTimeRange.fromDateTimeRange back.
KalenderTime Dates and times
A time of day, as an hour and a minute.
KalenderTimeRange Dates and times
Encapsulates a start and end KalenderTime that represents a day time range.